package serverFileManager;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class FileManager {

	public static final String FILEDIR = "./ServerFiles/";


	/**
	 * 
	 * @param mdbytes
	 */
	private static String printHash(String label, byte[] mdbytes){
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < mdbytes.length; i++) {
			sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
		}
		System.out.println(label+"\t\t**" + sb.toString()+"**");
		return sb.toString();
	}
	/**
	 * 
	 * @param s
	 * @return
	 */
	private static byte[] hexStringToByteArray(String s) {
		int len = s.length();
		byte[] data = new byte[len / 2];
		for (int i = 0; i < len; i += 2) {
			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
					+ Character.digit(s.charAt(i+1), 16));
		}
		return data;
	}

	/**
	 * 
	 * @param filename
	 * @param filecontent
	 * @return
	 */
	public static byte[] hashFile( byte[] filecontent) {
		MessageDigest md = null;
		try {
			md = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}		

		md.update(filecontent,0,filecontent.length);				
		byte[] filehash = md.digest();
		printHash("MD5 Hash: ", filehash);		
		//System.out.println("  mds" + md.getDigestLength());

		return filehash;			
	}

	/**	encryption using symmetric cryptography : AES algorithm
	 * 
	 * @param filecontent
	 * @param key
	 * @return
	 */
	private static byte[] encryptFile(String label, byte[] filecontent, byte[] AESkey){

		Cipher c = null;
		try {
			c = Cipher.getInstance("AES");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		}
		SecretKeySpec k = new SecretKeySpec(AESkey, "AES");
		try {
			c.init(Cipher.ENCRYPT_MODE, k);
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		}

		byte[] encryptedFile = null;

		try {
			encryptedFile = c.doFinal(filecontent);
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}

		printHash(label, encryptedFile);

		System.out.println(encryptedFile.length);

		return encryptedFile;				
	}
	/**
	 * 
	 * @param label
	 * @param filecontent
	 * @param AESkey
	 * @return
	 */

	private static byte[] decryptFile(String label, byte[] encfilecontent, byte[] AESkey){

		Cipher c = null;
		try {
			c = Cipher.getInstance("AES");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		}
		SecretKeySpec k = new SecretKeySpec(AESkey, "AES");
		try {
			c.init(Cipher.DECRYPT_MODE, k);
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		}

		byte[] decryptedFile = null;
		System.out.println("encfilecontent.length: "+encfilecontent.length);

		try {
			decryptedFile = c.doFinal(encfilecontent);
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}

		printHash(label, decryptedFile);
		return decryptedFile;				
	}
	/**
	 * 
	 * @param pathToCert
	 * @return
	 */
	private static  RSAPublicKey getRSApublicKey(String pathToCert) {
		RSAPublicKey rsapub = null ;

		try {
			InputStream is = new FileInputStream(pathToCert);
			CertificateFactory cf = CertificateFactory.getInstance("X.509");
			X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
			is.close();

			rsapub = (RSAPublicKey) cert.getPublicKey();


		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (CertificateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		printHash("RSA public key Exponent: ", rsapub.getPublicExponent().toByteArray());
		printHash("RSA public key Modulus: ", rsapub.getModulus().toByteArray());

		return rsapub;
	}
	/**
	 * 
	 * @param pathToCert
	 * @return
	 * @throws IOException 
	 * @throws CertificateException 
	 * @throws NoSuchAlgorithmException 
	 * @throws KeyStoreException 
	 * @throws UnrecoverableEntryException 
	 */	
	private static  RSAPrivateKey getRSAprivateKey(String keyStoreName) throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, UnrecoverableEntryException {

		KeyStore ks = KeyStore.getInstance("JKS");
		// to change
		char[] password = "moaad1988".toCharArray();
		java.io.FileInputStream fis =  new java.io.FileInputStream(keyStoreName);
		ks.load(fis,password);
		fis.close();

		KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) ks.getEntry("serverkey", new KeyStore.PasswordProtection(password));
		PrivateKey privateKey = pkEntry.getPrivateKey();
		RSAPrivateKey RSAPrivateKey = (RSAPrivateKey) privateKey;

		printHash("RSA private key Exponent: ", RSAPrivateKey.getPrivateExponent().toByteArray());
		printHash("RSA private key Modulus: ", RSAPrivateKey.getModulus().toByteArray());

		return RSAPrivateKey;
	}

	/**
	 * 
	 * @param key
	 * @param RSApub
	 * @return
	 */
	private static byte[] encryptKey(String label, byte[] key, RSAPublicKey RSApub){

		Cipher c = null;
		try {
			c = Cipher.getInstance("RSA");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		}
		try {
			c.init(Cipher.ENCRYPT_MODE, RSApub);
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		}
		byte[] cipherData = null;
		try {
			cipherData = c.doFinal(key);
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
		printHash(label, cipherData);

		return cipherData;
	}

	/**
	 * 
	 * @param label
	 * @param key
	 * @param RSApub
	 * @return
	 */
	private static byte[] decryptKey(String label, byte[] enckey, RSAPrivateKey RSAprv){

		Cipher c = null;
		try {
			c = Cipher.getInstance("RSA");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		}
		try {
			c.init(Cipher.DECRYPT_MODE, RSAprv);
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		}
		byte[] fileHash = null;
		try {
			fileHash = c.doFinal(enckey);
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
		printHash(label, fileHash);
		return fileHash;
	}


	/**
	 * 
	 * @param filename
	 * @param fileContent
	 * @throws IOException
	 */
	private static boolean checkFilePermission(String filename, String clientID, int duration) {
		try {
			filename = filename+".meta";
			File f = new File(FILEDIR + filename);
			FileInputStream fis = new FileInputStream(f);
			BufferedReader br = new BufferedReader(new InputStreamReader(fis));

			String strLine=null;
			
			while ((strLine = br.readLine()) != null) {
				String[] lineParts = strLine.split(" ");
				if (lineParts.length == 1 ) {			
					if (lineParts[0].equals(clientID)) {;
						br.close();
						fis.close();
						return true;
					}
				} else if (lineParts.length == 2 ) {
					if (lineParts[0].equals(clientID)) {
						long timeLimit = Long.parseLong(lineParts[1]);
						long currentTime = new java.util.Date().getTime();
						if ( timeLimit > currentTime + duration ) {
							br.close();
							fis.close();
							return true;
						}
					}
				}
			}
			br.close();
			fis.close();
			return false;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/** get the  decrypted bytes of encfilename
	 * this function include permissions checking
	 * @param encfilename	the name of the encrypted file
	 * @param clientID		the client who made the request
	 * @return	null if there is a problem or you do not have permission
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 * @throws CertificateException
	 * @throws KeyStoreException
	 * @throws UnrecoverableEntryException
	 */
	public static byte[] getFile(String encfilename, String clientID ) throws IOException, NoSuchAlgorithmException, CertificateException, KeyStoreException, UnrecoverableEntryException {
		if (!checkFilePermission(encfilename, clientID, 0)) { 
			System.out.println("You do not have the permission"); 
			return null;

		} else {
			byte[] encryAESkey =  getEncKey(encfilename);
			printHash("", encryAESkey);				
			//crypHash.getBytes();

			RSAPrivateKey RSAprv = getRSAprivateKey("./ServerFiles/SSLServerKeyStore/keystore.jks");
			byte[] fileHash =decryptKey("Decrypted FILE HASH", encryAESkey, RSAprv);

			//String encFilename = "/home/moaad/Desktop/Test/Encsource2";
			File file = new File(FILEDIR + encfilename);

			if (!file.exists()) {
				return null;	
			} else {
				InputStream is = new FileInputStream(file);
				byte[] buffer = new byte[(int) file.length()];
				is.read(buffer);
				byte[]  origfile = decryptFile("decryptedFile", buffer, fileHash);
				return origfile;
			}			
		}			
	}
	/** This file store the received file in encrypted format
	 * 
	 * @param owner			the owner of the file
	 * @param destfilename	the name of the file where will be stocked the received bytes
	 * @param fileContent	the received bfile bytes
	 * @throws IOException
	 */

	public static void storeFile(String owner, String destfilename, byte[] fileContent) throws IOException{		

		byte[] AESkey = FileManager.hashFile(fileContent);		
		RSAPublicKey RSApub = FileManager.getRSApublicKey("./ServerFiles/SSLServerKeyStore/serverCA.crt");
		byte[] encryptedFile = FileManager.encryptFile("FILE encrypted with FILE HASH ",fileContent, AESkey);
		byte[] AESkeyEnc = FileManager.encryptKey("HASH file encrypted with public key :", AESkey , RSApub);
		System.out.println(AESkeyEnc);

		File storageFile = new File(FILEDIR + destfilename);

		FileOutputStream fos = new FileOutputStream(storageFile, false);
		fos.write(encryptedFile);

		String metadataname = destfilename+".meta";
		File metastorageFile = new File(FILEDIR + metadataname);

		BufferedWriter out = new BufferedWriter(new FileWriter(metastorageFile, false));			
		out.write(owner);
		out.newLine();
		out.write( printHash("", AESkeyEnc) );
		out.close();
	}

	/**
	 * 
	 * @param filename
	 * @return
	 * @throws IOException 
	 */
	private static byte[] getEncKey(String filename) throws IOException{
		filename = filename+".meta";
		File f = new File(FILEDIR + filename);
		DataInputStream dis= new DataInputStream(new FileInputStream(f));
		BufferedReader br = new BufferedReader(new InputStreamReader(dis));

		String lastLine = "";
		String line = br.readLine();

		while ( line != null ) {
			lastLine = line;
			line = br.readLine();
		}
		
		br.close();
		dis.close();
		System.out.println("This is the RSA encypted hash file  strLine : "+ lastLine);
		byte[] encryAESkey = hexStringToByteArray(lastLine);

		return encryAESkey;
	}


	public static boolean delegateRights(String clientID, String destfilename, String receiver, int duration){		
		try {
			if ( !checkFilePermission(destfilename, clientID, duration) ) {
				return false;
			}

			String metadataname = destfilename + ".meta";
			File metastorageFile = new File(FILEDIR + metadataname);

			if(metastorageFile.exists()){

				FileInputStream fis = new FileInputStream(metastorageFile);
				BufferedReader br = new BufferedReader(new InputStreamReader(fis));
				LineNumberReader lnr = new LineNumberReader(br);

				String[] lines = new String[20];
				String line = lnr.readLine();
				int numberLines = 0;

				while ( line != null ) {
					numberLines = lnr.getLineNumber();
					lines[numberLines-1] = line;
					line = lnr.readLine();
				}

				System.out.println("**************nb de lignes : " + numberLines);

				lnr.close();
				br.close();
				fis.close();

				BufferedWriter bw = new BufferedWriter(new FileWriter(metastorageFile, false));

				for (int i = 0; i < numberLines - 1; i++) {
					bw.write(lines[i]);
					bw.newLine();
				}
				
				long timeLimit =  (new java.util.Date().getTime()) + duration;
				
				bw.write(receiver + " " + timeLimit);
				bw.newLine();
				bw.write(lines[numberLines-1]);

				bw.close();

				return true;

			} else {
				return false;

				//System.out.println(metastorageFile.createNewFile());
			}

		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}


	public static void main(String[] args) throws Exception{		

		String sourceFile = "/home/moaad/Desktop/Test/execute";	
		String encFile = "/home/moaad/Desktop/Test/Encexe";

		File f = new File(FILEDIR + sourceFile);		
		if (!f.exists()){
			System.err.println("FILE " + sourceFile + " DOES NOT EXIST");
			System.exit(-1);
		}
		byte[] fileContent = new byte[(int)f.length()];
		FileInputStream fis;
		try {
			fis = new FileInputStream(f);
			fis.read(fileContent);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}		

		storeFile("client7", encFile, fileContent);
		//byte orgFile[] = getFile(encFile, "client7");

		//System.out.println(printHash("", orgFile) + " : " + orgFile.length);		
	}
}
